<?php
/*
 * kernel/object.dbo.php 
 * 
 * This code is distributed and used under the GNU GPL v2 License,
 * please, read file LICENSE, which is situated in root directory,
 * or in "LICENSE" directory with most acceptable translation for you
 *
 * Database engine wrapper
 * 
 * Copyright (C) 2010, Taras Kudla
 * 
 */
# statements types
define("_SQL_SELECT",0);
define("_SQL_INSERT",1);
define("_SQL_UPDATE",2);
define("_SQL_DELETE",3);
define("_SQL_CREATETABLE",4);
define("_SQL_DROPTABLE",5);
define("_RETURN_SQL",6);
# sql results types
define("_RESULT_ASSOC",0);
define("_RESULT_LIST",1);
define("_RESULT_BOOL",2);
define("_RESULT_PAIRS",3);
define("_RESULT_ROW",4);
define("_RESULT_ONE",5);
define("_RESULT_NUMROW",6);
define("_RESULT_SINGLE",7);
# fields types definitions
define("_FIELD_CHAR","char");
define("_FIELD_CONSTCHAR","constchar");
define("_FIELD_INT","int");
define("_FIELD_BIGINTAUTO","bigint_auto");
define("_FIELD_INTAUTO","int_auto");
define("_FIELD_BIGINT","bigint");
define("_FIELD_TINYINT","tinyint");
define("_FIELD_SMALLINT","smallint");
define("_FIELD_DATE","date");
define("_FIELD_DATETIME","datetime");
define("_FIELD_TIME","time");
define("_FIELD_TIMESTAMP","timestamp");
define("_FIELD_LONGTEXT","longtext");
define("_FIELD_TEXT","text");
define("_FIELD_FLOAT","float");
define("_FIELD_REAL","real");
define("_FIELD_DOUBLE","double");
define("_FIELD_DECIMAL","decimal");
define("_FIELD_BLOB","blob");
define("_FIELD_BIT","bit");
define("_FIELD_BOOL","bool");
# connection types
define("_CON_MYSQL","mysql");
define("_CON_MYSQLI","mysqlig");
define("_CON_IBASE","ibase");
define("_CON_SQLITE","sqlite");
define("_CON_SQLITE3","sqlite3g");
define("_CON_PGSQL","pgsql");


# keys 
define("_KEY_PRIMARY","primary");
define("_KEY_UNIQUE","unique");
define("_KEY_INDEX","index");
define("_KEY_FULLTEXT","fulltext");
class gDbo extends gKernel {
    public  $driver=null;
    private $drvDir="adapters/database/";
    private $drvObj=null;
    private $totalSelect = 0;
    private $totalUpdate = 0;
    private $totalDelete = 0;
    private $totalDirect = 0;
    private $totalInsert = 0;
    private $totalSelectTime = 0;
    private $totalUpdateTime = 0;
    private $totalInsertTime = 0;
    private $totalDeleteTime = 0;
    private $totalDirectTime = 0;
    private $totalConnectTime=0;
    private $connected=array();
    # prefix for sql tables
    private $tablesprefix = "gepard_";
    # supported database engines
    private $supported=array("mysql","mysqli","pgsql","ibase","sqlite","sqlite3","mssql");
    # list of system tables, for permissions
    private $systables = array("functions","files","modules","groups","log","levels","levellinks","langterms","users","users_session","users_session_variable","systemoptions","output_areas");

    /**
     * Object initialization
     */
    public function init(){

    }
    /**
     * Connects to all databases specified in Gepard configuration
     *
     * @return boolean
     */
    public function connect(){
        $dbo = $this->core->get("dboconf");
        if(isset($dbo) && is_array($dbo) ) {
            foreach($dbo as $k=>$v){
                if(!in_array($k,$this->supported)){
                    $this->core->error("DBO","Driver '$k' is unsupported.");
                    return false;
                }
                else if( !is_array($v) || count($v)<1 ){
                    $this->core->error("DBO","Driver '$k' format is unsupported.");
                    return false;
                }
                else {
                    $this->load($k,$v);
                }
            }
        } else{
            throw new gException('DBO config file format is unsupported, cannot connect to database');
        }
        return true;
    }

    /**
     * Checks whether can connct to specified database configuration
     *
     * @param string $driver        Database engine name
     * @param array  $options       Database connection options
     * @return boolean
     */
    public function probeconnection($driver,$options){
        $tmp = $this->core->get("exceptiononerror");
        $this->core->set("exceptiononerror",true);
        try {
            $link=$this->load($driver,$options,true);
            $link->disconnect();
        } catch ( GepardException $e){
            $this->core->set("exceptiononerror",$tmp);
            return false;
        }
        $this->core->set("exceptiononerror",$tmp);
        return true;
    }

    /**
     *
     * @param <type> $driver
     * @param <type> $options
     * @param <type> $returnlink
     * @return gDatabaseConnector 
     */
    public function load($driver,$options,$returnlink=false){
        # checking if connected already
        if(in_array($driver,$this->connected) && $returnlink==false){
            $this->core->error("DBO","$driver already connected");
            return false;
        } else if( !in_array($driver,$this->supported)){
            $this->core->error("DBO","Database engine '$driver' is not supported");
            return false;
        }
        $servername=(isset($options["server"])?$options["server"]:"server:$driver");
        $this->core->debug("DBO","Trying connect to server ".$servername.", driver :".$driver);
        # checking file
        $link = $this->getAdapter($driver);
        //$adapter = $this->core->loadAdapter("database", $driver);
        if( !$link ){
            $this->core->error("DBO","Driver '$driver' file not found");
            return false;
        }
        $total = count($this->connected);
        # creating objects and links
        if( !($link instanceof gDatabaseConnector)){
            $this->core->error("DBO","Driver $driver is not an instance of gdbostruct interface");
            return false;
        }
        if( get_parent_class($link)==_DEFAULT_ABSTRACT_KERNEL)
            $link->init();
        # checking for options
        if( !isset($options["server"]))$options["server"]=null;
        if( !isset($options["db"]))$options["db"]=null;
        if( !isset($options["user"]))$options["user"]=null;
        if( !isset($options["password"]))$options["password"]=null;
        if( !isset($options["port"]))$options["port"]=null;
        $link->connect($options["server"],$options["port"],$options["db"],$options["user"],base64_decode($options["password"]));
        if( $returnlink == true){
            return $link;
        }
        if($total==0){
            $this->driver = $driver;
            $this->drvObj = &$link;
        }
        $this->$driver = &$link;
        # setting driver as connected
        $this->core->DEBUG("DBO","Driver connection:  ".$servername.", driver :".$driver." ... Connected");
        $this->connected[]=$driver;
        $this->core->debug("DBO",$driver." connection time ");
        return true;
    }
    /**
     * Returns link to connected database engine object
     *
     * @param <type> $contype
     * @return boolean
     */
    public function & getConnection($contype=null){
        $false=false;
        $num = count($this->connected);
        if( $num==0 ){
            $this->core->error("dbo","There are no alive database connections");
            return $false;
        }
        if( $num==1){
            return $this->driver;
        }
        return $this->$contype;
    }

    /**
     * Returns list of all connected databases
     *
     * @return array
     */
    public function getconnected(){
        return $this->connected;
    }

    /**
     * Returns list of all supported databases
     *
     * @return array
     */
    public function getsupported(){
        return $this->supported;
    }

    /**
     * Sets connected database engine as an object field
     *
     * @param string $key       db engine name
     * @param string $value     value for object field
     */
    function __set($key,$value){
        if(in_array($key,$this->supported)){
            $this->$key=$value;
            $this->core->DEBUG("DBO","Engine: ".$key." linked as source");
        }
    }

    /**
     *
     */
    public function prepare(){
        
    }
	
    /**
     * select sql statement handler
     *
     * @param mixed     $table
     * @param string    $fields
     * @param string    $sql
     * @param contant   $type
     * @return mixed
     */
    public function & select($table,$fields="*",$sql=null,$type=_RESULT_ASSOC){
        # checking if multitable query
        if( is_array($table)){
            $tablesql = null;
            $i=0;
            foreach( $table as $k=>$v){
                $tablesql .=($i>0?", ":" ").$k." as $v";
                $i++;
            }
            $table = $tablesql;
        }
        $result=$this->drvObj->select($table,$fields,$sql,$type);
        return $result;
    }

    /**
     *
     * @param <type> $table
     * @param array $data
     * @param <type> $returnid
     * @return <type> 
     */
    public function insert($table,array $data,$returnid=null){
        if( in_array(trim($table),$this->systables) && $this->core->isloaded("base") && !$this->core->base->checkmodpermission(_PERM_WRITESQL)){
            $this->core->error("dbo","Permission denied, module '".$this->core->getenvcaller("mod")."' cann't change system tables1");
            return false;
        }
        if( $this->core->get("dbodatareplication")==true && count($this->connected)>1){
            foreach($this->connected as $k=>$v){
                $result=$this->$v->insert($table,$data,$returnid);
            }
        } else
            $result=$this->drvObj->insert($table,$data,$returnid);
        return $result;
    }

    /**
     *
     * @param <type> $table
     * @param <type> $sql
     * @return <type> 
     */
    public function & update($table,$sql){
        if( in_array(trim($table),$this->systables) && $this->core->isloaded("base")  && !$this->core->base->checkmodpermission(_PERM_WRITESQL)){
            $this->core->error("dbo","Permission denied, module '".$this->core->getenvcaller("mod")."' cann't change system tables2");
            return false;
        }
        if( $this->core->get("dbodatareplication")==true && count($this->connected)>1){
            foreach($this->connected as $k=>$v){
                $this->$v->update($table,$sql);
            }
        } else
            $result=$this->drvObj->update($table,$sql);
        return $result;
    }

    /**
     * DELETE sql statement handler
     *
     * @param <type> $table
     * @param <type> $sql
     * @return <type> 
     */
    public function & delete($table,$sql){
        if( in_array(trim($table),$this->systables) && $this->core->isloaded("base")  && !$this->core->base->checkmodpermission(_PERM_WRITESQL)){
            $this->core->error("dbo","Permission denied, module '".$this->core->getenvcaller("mod")."' cann't change system tables3");
            return false;
        }
        if( $this->core->get("dbodatareplication")==true && count($this->connected)>1){
                foreach($this->connected as $k=>$v){
                    $this->$v->delete($table,$sql);
                }
        } else
            $result=$this->drvObj->delete($table,$sql);
        return $result;
    }

    /**
     *
     * @param <type> $sql
     * @return <type>
     */
    public function direct($sql){
        $this->totalDirect++;
        print "DBO statement 'direct' - deprecated";
        return $this->drvObj->execute($sql);
    }

    /**
     *
     * @param <type> $sql
     * @return <type> 
     */
    public function execute($sql){
        $this->totalDirect++;
        return $this->drvObj->execute($sql);
    }
		
	public function createtable(sqltable $map,$returnsql=false){
		//if( in_array(trim($map->getname()),$this->systables) && $this->core->isloaded("base")  && !$this->core->base->checkmodpermission(_PERM_WRITESQL)){
		//	$this->core->error("dbo","Permission denied, module '".$this->core->getenvcaller("mod")."' cann't change system tables");
		//	return false;
		//}
                $data = null;
		if( $this->core->get("dbodatareplication")==true && count($this->connected)>1){
			foreach($this->connected as $k=>$v){
				$data[]=$this->$v->createtable($map);
			}
		} else
		$data = $this->drvObj->createtable($map,$returnsql);
		return $data;
	}
	
	public function droptable($table){
		//if( in_array(trim($table),$this->systables) && $this->core->isloaded("base")  && !$this->core->base->checkmodpermission(_PERM_WRITESQL)){
		//	$this->core->error("dbo","Permission denied, module '".$this->core->getenvcaller("mod")."' cann't change system tables");
		//	return false;
		//}
		if( $this->core->get("dbodatareplication")==true && count($this->connected)>1){
			foreach($this->connected as $k=>$v){
				$this->$v->droptable($table);
			}			
		} else		
		$this->drvObj->droptable($table);
	}
	
	public function gettables($database=null){
		$this->totalDirect++;
		return $this->drvObj->gettables($this->core->escapeString($database));
	}
	
	public function getkeys($table){
		$this->totalDirect++;
		return $this->drvObj->getkeys($table);
	}	
	
	public function getdatabases(){
		$this->totalDirect++;
		return $this->drvObj->getdatabases();
	}
	public function getfields($table){
		$this->totalDirect++;
		return $this->drvObj->getfields($table);
	}

	public function getdatabase(){
		$this->totalDirect++;
		return $this->drvObj->getdatabase();
	}
	
	/*
	 * TODO: 
	 */
	public function getstats(){
		$stats=array();
		$total = $this->totalSelect+$this->totalUpdate+$this->totalDelete+$this->totalInsert+$this->totalDirect;
		$totaltime=$this->totalDirectTime+$this->totalSelectTime+$this->totalInsertTime+$this->totalUpdateTime+$this->totalDeleteTime;
		$stats["totalnum"]=$total;
		$stats["totaltime"]=$totaltime;
		$stats["datareplication"]=($this->core->get("dbodatareplication"))?"true":"false";
		$stats["totalconnected"]=count($this->connected);
		$stats["connected"]=$this->connected;
		$stats["connectime"]=$this->totalConnectTime;
		$stats["Select"]=array("num"=>$this->totalSelect,"time"=>0);
		$stats["Update"]=array("num"=>$this->totalUpdate,"time"=>0);
		$stats["Insert"]=array("num"=>$this->totalInsert,"time"=>0);
		$stats["Delete"]=array("num"=>$this->totalDelete,"time"=>0);
		$stats["Direct"]=array("num"=>$this->totalDirect,"time"=>0);
		return $stats;
	}
	public function tableexists($table){
		$this->totalDirect++;
		return $this->drvObj->tableexists($table);	
	}
	
	public function version(){
		return $this->drvObj->version();
	}
	
	public function engine(){
		return $this->connected[0];
	}
	# disconnecting all connections	
	public function disconnect($dbdrv = null)
		{
		if($dbdrv!=null && in_array($dbdrv,$this->connected) ){
			is_object($this->$dbdrv)?$this->$dbdrv->disconnect():null;
			
			unset($this->connected[array_search($dbdrv,$this->connected)]);
			return true;
		}		
		foreach($this->connected as $k=>$v){
		
			is_object($this->$v)?$this->$v->disconnect():null;
			unset($this->connected[$k]);
		}
		
		}
	
	function __destruct(){
	}

	}
/*
 * Default sqltable class, use it for tables creating
 */
	final class sqltable{
		private $name = "";
		private $fields = array();
		private $keys	= array();
		private $encoding = null;
		private $engine		= null;
		public  $droponexist = true;
	function __construct($name){
		$this->name = $name;
	}
	public function addfield($name,$type,$size=null,$default=null,$null=false){
		$this->fields[]=array($name,$type,$size,$default,$null);
	}
	
	public function addkey($type,$fieldname){
		$this->keys[]=array($type,$fieldname);		
	}
	
	public function getname(){
		return $this->name;
	}
	
	public function get(){
		return array($this->name,$this->fields,$this->keys,$this->encoding,$this->engine);			
	}
	public function encoding($encoding){
		$this->encoding = $encoding;
	}
	public function engine($engine){
		$this->engine	= $engine;
	}
	
	function __destruct(){
		
	}
}

class gActiveRecord {
    
}